package upbeat;

import java.util.LinkedList;

import upbeat.types.Measure;
import upbeat.types.MusicPhrase;

/**
 * Generate music for each Musician.
 * 
 * @author Fan Lin
 * 
 */
public class Track {
	static int maxVol = 100;
	static int minVol = 0;
	static int maxPitch = 36;
	static int minPitch = -36;
	static int maxInst = 127;
	static int minInst = 0;

	private LinkedList<Measure> measureBuffer; 
	private int passedMeasure;
	private MusicPhrase currentPhrase;
	
	public Track() {
		measureBuffer = new LinkedList<Measure>();
		passedMeasure = 1;
		currentPhrase = MusicPhrase.getREST();
		currentPhrase.setRepeat(-1);
		
	}
	
	public void appendPhraseAt(MusicPhrase p, int measureNum) throws Exception
	{
		playForwardTo(measureNum);
		p.reset();
		currentPhrase = p;
	}

	private void playForwardTo(int measureNum) throws Exception {
		int targetBufferSize = measureNum - passedMeasure;
		if (targetBufferSize < 0) {
			throw new Exception("Cannot change measures already produced.");
		}
		
		while (targetBufferSize < measureBuffer.size()) {
			measureBuffer.removeLast();
		}
		
		while (targetBufferSize > measureBuffer.size()) {
			
			Measure m = currentPhrase.playNext();
			if (m != null) { 
				measureBuffer.addLast(m);
			} else {
				measureBuffer.addLast(Measure.REST);
			}
		}
	}
	
	public Measure getNextMeasure() {
		passedMeasure++;
		Measure m = Measure.REST;
		
		if (measureBuffer.size() > 0) {
			m = measureBuffer.removeFirst();
		} else {
			Measure t = currentPhrase.playNext();
			if (t != null) {
				m = t;
			}
		}
		if (!m.getIsReady()) {
			m.setInstrument(instrumentId);
			m.setVolume(volume);
			m.setPitch(pitch);
			m.setIsReady(true);
		}
		return m;
	}
	
	private int volume = 100;
	private int pitch = 0;
	private int instrumentId = 0;
	
	private void setParameterBefore(int measureNum) {
		try {
			playForwardTo(measureNum);
		} catch (Exception e) {
			return;
		}
		
		for (Measure m : measureBuffer) {
			if (!m.getIsReady()) {
				m.setVolume(volume);
				m.setPitch(pitch);
				m.setInstrument(instrumentId);
				m.setIsReady(true);
			}
		}
	}
	
	public void changeVolumeTo(int volume, int measureNum) {
		if(volume > maxVol) {
			System.err.println("Warning: attempt to set volume to " + volume + " > " + maxVol + ".  Using " + maxVol + ".");
			volume = maxVol;
		}
		if(volume < minVol) {
			System.err.println("Warning: attempt to set volume to " + volume + " < " + minVol + ".  Using " + minVol + ".");
			volume = minVol;
		}
		setParameterBefore(measureNum);
		this.volume = volume;
	}
	
	public void changeVolumeBy(int volume, int measureNum) {
		setParameterBefore(measureNum);
		this.volume += volume;
		if(this.volume > maxVol) {
			System.err.println("Warning: attempt to set volume to " + this.volume + " > " + maxVol + ".  Using " + maxVol + ".");
			this.volume = maxVol;
		}
		if(this.volume < minVol) {
			System.err.println("Warning: attempt to set volume to " + this.volume + " < " + minVol + ".  Using " + minVol + ".");
			this.volume = minVol;
		}
	}
	
	public void changePitchTo(int pitch, int measureNum) {
		if(pitch > maxPitch) {
			System.err.println("Warning: attempt to set pitch to " + pitch + " > " + maxPitch + ".  Using " + maxPitch + ".");
			pitch = maxPitch;
		}
		if(pitch < minPitch) {
			System.err.println("Warning: attempt to set pitch to " + pitch + " < " + minPitch + ".  Using " + minPitch + ".");
			pitch = minPitch;
		}
		setParameterBefore(measureNum);
		this.pitch = pitch;
		
	}
	public void changePitchBy(int pitch, int measureNum) {
		setParameterBefore(measureNum);
		this.pitch += pitch;
		if(this.pitch > maxPitch) {
			System.err.println("Warning: attempt to set pitch to " + this.pitch + " > " + maxPitch + ".  Using " + maxPitch + ".");
			this.volume = maxPitch;
		}
		if(this.pitch < minPitch) {
			System.err.println("Warning: attempt to set pitch to " + this.pitch + " < " + minPitch + ".  Using " + minPitch + ".");
			this.volume = minPitch;
		}
	}
	
	public void changeInstrumentTo(int instrumentId, int measureNum) {
		if(instrumentId > maxInst) {
			System.err.println("Warning: attempt to set instrument to " + instrumentId + " > " + maxInst + ".  Using " + maxInst + ".");
			instrumentId = maxInst;
		}
		if(instrumentId < minInst) {
			System.err.println("Warning: attempt to set instrument to " + instrumentId + " < " + minInst + ".  Using " + minInst + ".");
			instrumentId = minInst;
		}
		setParameterBefore(measureNum);
		this.instrumentId = instrumentId;
	}
	
	public void changeInstrumentBy(int instrumentId, int measureNum) {
		setParameterBefore(measureNum);
		this.instrumentId += instrumentId;
		if(this.instrumentId > maxInst) {
			System.err.println("Warning: attempt to set instrument to " + this.instrumentId + " > " + maxInst + ".  Using " + maxInst + ".");
			this.instrumentId = maxInst;
		}
		if(this.instrumentId < minInst) {
			System.err.println("Warning: attempt to set instrument to " + this.instrumentId + " < " + minInst + ".  Using " + minInst + ".");
			this.instrumentId = minInst;
		}
	}
}
